﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using BurningSphere.Simulator.Properties;
using System.IO;
using System.Xml;
using BurningSphere.Simulator.MIDI;

namespace BurningSphere.Simulator
{
    public partial class Form1 : Form
    {
		public static MidiListener Midi;

		string errorString = "";
		Lasers lasers = new Lasers();
		FileSystemWatcher fileWatcher;

        bool initialiseGeometry = true;
		bool reloadLasers = true;
        Device device;
		Microsoft.DirectX.Direct3D.Font statsFont;
		Sprite textSprite;
		Texture texture;
		Texture skyTexture;
        Sprite laserSprite;
        Surface surface;
		Mesh earthMesh;
		Mesh skyMesh;
		MatrixStack matrixStack;
        Material whiteMaterial;
		Material fadeMaterial;
        VertexBuffer laserQuad;
		VertexBuffer fadeQuad;
		Matrix earthMatrix;
		Matrix quadMatrix;
		float fps = 0;
		double renderAccumulatedTime = 0.0;
		double updateAccumulatedTime = 1000.0;

		List<Shape> shapes = new List<Shape>();

        float speedmodifier = 1.0f;
        bool orbitOn = true;
        float earthSpin = 180.0f;

		Line line;

        struct Vertex
        {
            public float x, y, z; // Position of vertex in 3D space
            public int color;     // Diffuse color of vertex

            public Vertex(float _x, float _y, float _z, int _color)
            {
                x = _x; y = _y; z = _z;
                color = _color;
            }
        };

        /// <summary>
        /// Ctor
        /// </summary>
        public Form1()
        {
            InitializeComponent();
            this.ClientSize = new System.Drawing.Size(1280, 1024);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

		//	System.Environment.CurrentDirectory = @"D:\Dev\Projects\Burning Sphere\BurningSphere.Simulator\BurningSphere.Simulator";

			string path = System.Environment.CurrentDirectory + @"\Data";
			fileWatcher = new FileSystemWatcher(path);
			fileWatcher.Filter = "Lasers.xml";
			fileWatcher.Changed += new FileSystemEventHandler(FileWatcherChanged);
			fileWatcher.EnableRaisingEvents = true;


			Midi = new MidiListener();
			Midi.Init();
			Midi.Start();

			FrameworkTimer.Start();
		}

		protected override void OnClosed(EventArgs e)
		{
			if (Midi != null)
			{
				Midi.Stop();
				Midi.Close();
				Midi.Dispose();
				Midi = null;
			}

			base.OnClosed(e);
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing && (components != null))
			{
				components.Dispose();
			}
			base.Dispose(disposing);
		}

		/// <summary>
		/// File changed event
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void FileWatcherChanged(object sender, FileSystemEventArgs e)
		{
			reloadLasers = true;
		}

        /// <summary>
        /// On paint
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
			double deltaTime = FrameworkTimer.GetElapsedTime();
			renderAccumulatedTime += deltaTime;
			updateAccumulatedTime += deltaTime;

			fps = (float)(1.0 / deltaTime);

			if (updateAccumulatedTime >= 1.0 / Settings.Default.UpdateFPS)
			{
				this.OnUpdate((float)updateAccumulatedTime);
				updateAccumulatedTime = 0.0;
			}

			if (renderAccumulatedTime < 1.0 / Settings.Default.RenderFPS)
			{
				this.Invalidate();
			}
			else
			{
				renderAccumulatedTime = 0.0;
				this.Render();
				this.Invalidate();
				FrameworkTimer.Start();
			}
        }

        /// <summary>
        /// Key handler
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case System.Windows.Forms.Keys.Escape:
                    this.Dispose();
                    break;

                case System.Windows.Forms.Keys.Space:
                    orbitOn = !orbitOn;
                    break;

                case System.Windows.Forms.Keys.F1:
                    ++speedmodifier;
                    break;

                case System.Windows.Forms.Keys.F2:
                    --speedmodifier;
                    break;
            }
        }

        /// <summary>
        /// This method basically creates and initialize the Direct3D device and
        /// anything else that doens't need to be recreated after a device 
        /// reset.
        /// </summary>
        public void Init()
        {
            // Do we support hardware vertex processing? If so, use it. 
            // If not, downgrade to software.
            CreateFlags flags;
            Caps caps = Manager.GetDeviceCaps(Manager.Adapters.Default.Adapter, DeviceType.Hardware);
            if (caps.DeviceCaps.SupportsHardwareTransformAndLight)
            {
                flags = CreateFlags.HardwareVertexProcessing;
            }
            else
            {
                flags = CreateFlags.SoftwareVertexProcessing;
            }

            // Create a simple, windowed device.
            PresentParameters d3dpp = new PresentParameters();

            d3dpp.BackBufferFormat = Format.Unknown;
            d3dpp.SwapEffect = SwapEffect.Discard;
            d3dpp.Windowed = true;
            d3dpp.EnableAutoDepthStencil = true;
            d3dpp.AutoDepthStencilFormat = DepthFormat.D16;
            d3dpp.PresentationInterval = PresentInterval.Immediate;

            device = new Device(0, DeviceType.Hardware, this, flags, d3dpp);

            // Register event-handlers
            device.DeviceReset += new System.EventHandler(this.OnResetDevice);
            device.DeviceLost += new EventHandler(OnLostDevice);
            device.DeviceResizing += new CancelEventHandler(OnDeviceResizing);
            device.Disposing += new EventHandler(OnDestroyDevice);

            Device.IsUsingEventHandlers = false;

            OnResetDevice(device, null);
        }

        /// <summary>
        /// Destroy device
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnDestroyDevice(object sender, EventArgs e)
        {
            if (earthMesh != null)
            {
                earthMesh.Dispose();
                earthMesh = null;
            }
			if (skyMesh != null)
			{
				skyMesh.Dispose();
				skyMesh = null;
			}
		}

        /// <summary>
        /// Device resizing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void OnDeviceResizing(object sender, CancelEventArgs e)
        {
            OnResetDevice(sender, null);
        }

        /// <summary>
        /// Device lost
        /// </summary>
        void OnLostDevice(object sender, EventArgs e)
        {
            if (laserSprite != null)
            {
                laserSprite.Dispose();
                laserSprite = null;
            }

            if (texture != null)
            {
                texture.Dispose();
                texture = null;
            }

			if (skyTexture != null)
			{
				skyTexture.Dispose();
				skyTexture = null;
			}

			if (textSprite != null)
			{
				textSprite.Dispose();
				textSprite = null;
			}

			lasers.OnLostDevice();

			if (line != null)
			{
				line.Dispose();
				line = null;
			}
        }

        /// <summary>
        /// Initialise Geometry
        /// </summary>
        private void InitialiseGeometry()
        {
            if (earthMesh != null)
            {
                earthMesh.Dispose();
            }
			if (skyMesh != null)
			{
				skyMesh.Dispose();
			}
			if (laserSprite != null && !laserSprite.Disposed)
			{
				laserSprite.Dispose();
			}
			if (laserQuad != null)
			{
				laserQuad.Dispose();
			}
			if (fadeQuad != null)
			{
				fadeQuad.Dispose();
			}
            earthMesh = TexturedMesh.Sphere(device, 1.0f, 50, 50);
			skyMesh = TexturedMesh.Sphere(device, 10.0f, 20, 20,true);
			laserQuad = CreateTexturedQuad();
			fadeQuad = CreateColouredQuad(Color.Tomato.ToArgb());
			laserSprite = new Sprite(device);

			lasers.Initialise(device);

			if (line != null)
			{
				line.Dispose();
			}
			line = new Line(device);
			line.Width = 2;
			line.Antialias = true;


            initialiseGeometry = false;
        }

        /// <summary>
        /// This event-handler is a good place to create and initialize any 
        /// Direct3D related objects, which may become invalid during a 
        /// device reset.
        /// </summary>
        public void OnResetDevice(object sender, EventArgs e)
        {
            device = (Device)sender;
            device.Transform.Projection =
                Matrix.PerspectiveFovLH(Geometry.DegreeToRadian(45.0f),
                (float)this.ClientSize.Width / this.ClientSize.Height,
                0.1f, 100.0f);

            device.RenderState.Lighting = false;
            device.RenderState.CullMode = Cull.None;
            device.RenderState.FillMode = FillMode.WireFrame;

            // Load the textures
            if (texture != null)
            {
                texture.Dispose();
            }
			if (skyTexture != null)
			{
				skyTexture.Dispose();
			}
			skyTexture = TextureLoader.FromFile(
				device,
				System.Windows.Forms.Application.StartupPath + @"\"+Settings.Default.SkyTexture
				);

            texture = new Texture(
                device, 
                Settings.Default.SphereSurfaceDimension, 
                Settings.Default.SphereSurfaceDimension, 
                0, Usage.RenderTarget, Format.X8R8G8B8, Pool.Default
				);

            surface = texture.GetSurfaceLevel(0);

            whiteMaterial = new Material();
            whiteMaterial.Ambient = Color.FromArgb(255, 255, 255, 255);
            whiteMaterial.Diffuse = whiteMaterial.Ambient;

			fadeMaterial = new Material();
			fadeMaterial.Ambient = Color.FromArgb(255, 1, 1, 1);
			fadeMaterial.Diffuse = fadeMaterial.Ambient;

            // Create a matrix stack...
            matrixStack = new MatrixStack();

            // Light our way
            device.Lights[0].Type = LightType.Directional;
			device.Lights[0].Diffuse = Color.FromArgb(255, 255, 255);
            device.Lights[0].Direction = new Vector3(.3f, -2, -.2f);
            device.Lights[0].Enabled = true;
            device.Lights[0].Update();

			// Initialize the stats font
			statsFont = ResourceCache.GetGlobalInstance().CreateFont(device, 15, 0, FontWeight.Bold, 1, false, CharacterSet.Default,
				Precision.Default, FontQuality.Default, PitchAndFamily.FamilyDoNotCare | PitchAndFamily.DefaultPitch
				, "Arial");

			textSprite = new Sprite(device);

            initialiseGeometry = true;
        }

        /// <summary>
        /// Update method
        /// </summary>
        public void OnUpdate(float elapsedTime)
        {
            if (initialiseGeometry)
            {
                InitialiseGeometry();
            }

			if (reloadLasers)
			{
				try
				{
					lasers.Shutdown();
					lasers.Load("Data\\Lasers.xml");
					lasers.Initialise(device);
					reloadLasers = false;
					errorString = "";
				}
				catch (Exception e)
				{
					reloadLasers = false;
					lasers.Shutdown();
					errorString = e.Message;
				}
			}

            if (orbitOn == true)
            {
                earthSpin += speedmodifier * (elapsedTime * 10.0f);
            }

			Matrix earthPoleRotate = Matrix.Identity;
			Matrix earthScale = Matrix.Identity;
            Matrix earthSpinRotation = Matrix.Identity;
			earthMatrix = Matrix.Identity;

			earthPoleRotate.RotateX(Geometry.DegreeToRadian(270));
			earthSpinRotation.RotateY(Geometry.DegreeToRadian(earthSpin));
			earthScale.Scale(Settings.Default.SphereRadius, Settings.Default.SphereRadius, Settings.Default.SphereRadius);

            // Now, concatenate them together...
            earthMatrix =	earthPoleRotate *
						   earthScale *              // 1. Uniformly scale the Earth up in size
						   earthSpinRotation;        // 2. and then spin it on its axis.

			quadMatrix = Matrix.Identity;
			Matrix quadMatrixRotate = Matrix.Identity;
			Matrix quadMatrixScale = Matrix.Identity;
			Matrix quadMatrixPosition = Matrix.Identity;
			quadMatrixRotate.RotateY(Geometry.DegreeToRadian(earthSpin));
			quadMatrixScale.Scale(10, 10, 10);
			quadMatrixPosition.Translate(0, 0, 0);

			quadMatrix = quadMatrixScale * quadMatrixRotate * quadMatrixPosition;

			lasers.Update();
		}

        /// <summary>
        /// Render
        /// </summary>
        private void Render()
        {
            RenderSurface(device);

            device.Transform.World = Matrix.Identity;

            bool sceneBegan = false;
			try
			{
				device.BeginScene();
				sceneBegan = true;

				// Now we can clear just view-port's portion of the buffer to red...
				device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.SkyBlue, 1.0f, 0);


				// Have the view matrix move the view move us to a good vantage point so 
				// we can see the Earth 
				device.Transform.View = Matrix.LookAtLH(new Vector3(0.0f, 2.0f, -25.0f),    // Camera position
															new Vector3(0.0f, 0.0f, 0.0f),   // Look-at point
															new Vector3(0.0f, 1.0f, 0.0f));  // Up vector

				device.RenderState.CullMode = Cull.None;
				device.RenderState.ZBufferEnable = false;
				device.Transform.World = earthMatrix;
				device.SetTexture(0, skyTexture);
				skyMesh.DrawSubset(0);
				device.RenderState.ZBufferEnable = true;

				device.RenderState.Lighting = true;
				device.RenderState.AlphaBlendEnable = false;
				device.Material = whiteMaterial;
				device.RenderState.Ambient = Color.FromArgb(255, 255, 255);
				device.RenderState.Wrap0 = WrapCoordinates.Zero;
				device.RenderState.FillMode = FillMode.Solid;
				device.RenderState.CullMode = Cull.CounterClockwise;

				// quad
		//		device.Transform.World = quadMatrix;
		//		device.SetTexture(0, laserTexture);
		//		device.VertexFormat = CustomVertex.PositionNormalTextured.Format;
		//		device.SetStreamSource(0, laserQuad, 0);
		//		device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);

				// earth

				device.RenderState.AlphaBlendEnable = true;
				device.RenderState.SourceBlend = Blend.One;
				device.RenderState.DestinationBlend = Blend.One;
				device.RenderState.BlendOperation = BlendOperation.Add;

				device.Transform.World = earthMatrix;
				device.SetTexture(0, texture);
//				device.SetTexture(0, laserTexture);
//				device.SetTexture(1, texture);
				earthMesh.DrawSubset(0);


				RenderText();

			}
			catch
			{
				throw;
			}
            finally
            {
                if (sceneBegan)
                {
                    device.EndScene();
                    device.Present();
                }
            }
        }

		private void RenderSurface(Device device)
		{
			//Save the current render target so that we can reset it after use.
			Surface oldRenderTarget = device.GetRenderTarget(0);

			//Set the render target to the surface we created
			device.SetRenderTarget(0, surface);

			//Clear the render target (our surface and our depth buffer of the surface) 
			device.BeginScene();
			device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.FromArgb(30,40,50), 1.0f, 0);
			device.RenderState.Ambient = Color.FromArgb(255, 255, 255);
			device.RenderState.Lighting = true;
			device.RenderState.AlphaBlendEnable = false;
			device.Material = whiteMaterial;
			device.RenderState.Ambient = Color.FromArgb(255, 255, 255);
			device.RenderState.Wrap0 = WrapCoordinates.Zero;
			device.RenderState.FillMode = FillMode.Solid;
			device.RenderState.CullMode = Cull.CounterClockwise;


	/*		line.Begin();

			device.RenderState.CullMode = Cull.None;
			device.RenderState.AlphaBlendEnable = true;
			device.RenderState.SourceBlend = Blend.One;
			device.RenderState.DestinationBlend = Blend.One;

			Vector3[] vertices = new Vector3[2];
			vertices[0] = new Vector3(0, -0.95f, 0);
			vertices[1] = new Vector3(0, 0.95f, 0);
			line.DrawTransform(vertices, Matrix.Identity, Color.White.ToArgb());
			line.End();
			device.RenderState.AlphaBlendEnable = false;*/

			/*
			using (Sprite sprite = new Sprite(device))
			{

				Queue<LaserHistory>.Enumerator iter = laserHistory.GetEnumerator();
				while (iter.MoveNext())
				{
					sprite.Begin(SpriteFlags.AlphaBlend);
					sprite.Draw(laserTexture,
						new Vector3(0, 0, 0), new Vector3(iter.Current.X, iter.Current.Y, 0),
							iter.Current.Colour.ToArgb()
							);
					sprite.End();

					iter.Current.Fade();
				}

			}
			*/

			lasers.Render(device);

			// quad
//			device.RenderState.AlphaBlendEnable = true;
//			device.RenderState.AlphaBlendOperation = BlendOperation.RevSubtract;
//			device.RenderState.AlphaSourceBlend = Blend.One;
//			device.RenderState.AlphaDestinationBlend = Blend.One;

//			device.Transform.World = Matrix.Identity;
//			device.VertexFormat = CustomVertex.PositionColored.Format;
//			device.SetStreamSource(0, fadeQuad, 0);
//			device.SetTexture(0, null);
//			device.Material = fadeMaterial;
//			device.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);

			device.EndScene();

			//Reset the render target. 
			device.SetRenderTarget(0, oldRenderTarget);
			oldRenderTarget.Dispose();
		}

		/// <summary>
		/// Render the help and statistics text. This function uses the Font object for 
		/// efficient text rendering.
		/// </summary>
		private void RenderText()
		{
			TextHelper txtHelper = new TextHelper(statsFont, textSprite, 15);

			// Output statistics
			txtHelper.Begin();
			txtHelper.SetInsertionPoint(5, 5);
			txtHelper.SetForegroundColor(System.Drawing.Color.Black);
			txtHelper.SetForegroundColor(System.Drawing.Color.White);

			txtHelper.DrawTextLine(String.Format("FPS: {0:F2}",fps) );
			txtHelper.DrawTextLine(String.Format("MIDI: {0}", Midi.NumChanMsgs));

			if (errorString != "")
			{
				txtHelper.DrawTextLine(errorString);
			}
			txtHelper.End();
		}

        private VertexBuffer CreateQuad()
        {
            //create quad
            CustomVertex.PositionNormal[] verts = new CustomVertex.PositionNormal[4];
            verts[0] = new CustomVertex.PositionNormal(-1f, 1f, 0f, 0f, 1f, 0f);
            verts[1] = new CustomVertex.PositionNormal(1f, 1f,  0f, 0f, 1f, 0f);
            verts[3] = new CustomVertex.PositionNormal(1f, -1f, 0f, 0f, 1f, 0f);
            verts[2] = new CustomVertex.PositionNormal(-1f, -1f, 0f, 0f, 1f, 0f);
            VertexBuffer quadVertices = new VertexBuffer(
                typeof(CustomVertex.PositionNormal), 4, device, 0,
                CustomVertex.PositionNormal.Format, Pool.Default
                );
            GraphicsStream stm = quadVertices.Lock(0, 0, 0);
            stm.Write(verts);
            quadVertices.Unlock();

            return quadVertices;
        }

		private VertexBuffer CreateColouredQuad(int colour)
		{
			//create quad
			CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[4];
			verts[0] = new CustomVertex.PositionColored(-1f, 1f, 0f, colour);
			verts[1] = new CustomVertex.PositionColored(1f, 1f, 0f, colour);
			verts[3] = new CustomVertex.PositionColored(1f, -1f, 0f, colour);
			verts[2] = new CustomVertex.PositionColored(-1f, -1f, 0f, colour);
			VertexBuffer quadVertices = new VertexBuffer(
				typeof(CustomVertex.PositionColored), 4, device, 0,
				CustomVertex.PositionColored.Format, Pool.Default
				);
			GraphicsStream stm = quadVertices.Lock(0, 0, 0);
			stm.Write(verts);
			quadVertices.Unlock();

			return quadVertices;
		}


        private VertexBuffer CreateTexturedQuad()
        {
            //create quad
            CustomVertex.PositionNormalTextured[] verts = new CustomVertex.PositionNormalTextured[4];
			verts[0] = new CustomVertex.PositionNormalTextured(-3f, -2f, 3f, 0f, 1f, 0f, 1f, 1f);
			verts[1] = new CustomVertex.PositionNormalTextured(3f, -2f, 3f, 0f, 1f, 0f, 1f, 0f);
			verts[3] = new CustomVertex.PositionNormalTextured(3f, -2f, -3f, 0f, 1f, 0f, 0f, 0f);
			verts[2] = new CustomVertex.PositionNormalTextured(-3f, -2f, -3f, 0f, 1f, 0f, 0f, 1f);
			VertexBuffer quadVertices = new VertexBuffer(typeof(CustomVertex.PositionNormalTextured), 4, device, 0, CustomVertex.PositionNormalTextured.Format, Pool.Default);
            GraphicsStream stm = quadVertices.Lock(0, 0, 0);
            stm.Write(verts);
            quadVertices.Unlock();

            return quadVertices;
        }
    }
}
